package com.ccys.netty.config;

import com.ccys.netty.handler.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpObjectDecoder;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.client.discovery.event.HeartbeatEvent;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.concurrent.TimeUnit;


/**
 * Netty设置
 * 参考 https://blog.csdn.net/qq_38038507/article/details/132619446
 *
 * netty 使用实例
 * https://blog.csdn.net/zch981964/article/details/130631162
 * netty 官方api文档
 * https://netty.io/4.1/api/index.html
 *
 */
@Slf4j
@Configuration
@Component
public class NettyServer{

    public static int port;

    @Autowired
    private WebSocketHandler webSocketHandler;

    @Autowired
    private HttpServerHandler httpServerHandler;

    @Value("${netty.port}")
    public void setPort(int port) {
        NettyServer.port = port;
    }

    public void start() throws Exception {
        //创建两个线程组 boosGroup、workerGroup
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //创建服务端辅助启动类ServerBootstrap对象
            ServerBootstrap serverBootstrap = new ServerBootstrap();

            //设置NIO线程组
            serverBootstrap.group(bossGroup, workerGroup);
            //设置服务端通道实现类型
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
            //serverBootstrap.localAddress(new InetSocketAddress(port));
            //设置队列大小
            serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024)
            // 两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
            .childOption(ChannelOption.SO_KEEPALIVE, true);

            serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    /*//设置LineBasedFrameDecoder处理器
                    ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                    //设置StringDecoder处理器
                    ch.pipeline().addLast(new StringDecoder(Charset.forName("GBK")));
                    ch.pipeline().addLast(new CustomHandler());
                    ch.pipeline().addLast(new StringEncoder(Charset.forName("UTF-8")));
                    ch.pipeline().addLast("handler",new IdleStateHandler(60,0,0, TimeUnit.SECONDS));*/
                    // 流水线管理通道中的处理程序（Handler），用来处理业务
                    // webSocket协议本身是基于http协议的，所以这边也要使用http编解码器
                    // ch.pipeline().addLast(new HttpServerCodec());
                    //TODO  是一个编码器（Encoder），用于将 Java 对象序列化并编码为字节流，以便进行网络传输。当你向 ChannelPipeline 中添加 ObjectEncoder 时，它会负责将需要发送的 Java 对象转换为字节流并发送到网络中。
                    //ch.pipeline().addLast(new ObjectEncoder());
                    //ch.pipeline().addLast(new StringDecoder());

                    // 以块的方式来写的处理器 TODO
                    //ch.pipeline().addLast(new ChunkedWriteHandler());
                    /*
                    说明：
                    1、http数据在传输过程中是分段的，HttpObjectAggregator可以将多个段聚合
                    2、这就是为什么，当浏览器发送大量数据时，就会发送多次http请求
                    处理消息最大长度
                     */
                    //TODO
                    //ch.pipeline().addLast(new HttpObjectAggregator(8192));

                    //针对客户端，若10s内无读事件则触发心跳处理方法HeartBeatHandler#userEventTriggered
                    ch.pipeline().addLast(new IdleStateHandler(10, 0, 0));
                    //自定义空闲状态检测(自定义心跳检测handler)
                    ch.pipeline().addLast(new HeartBeatHandler());
                    /*
                    说明：
                    1、对应webSocket，它的数据是以帧（frame）的形式传递
                    2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
                    3、核心功能是将http协议升级为ws协议，保持长连接
                    */
                    //ch.pipeline().addLast(new WebSocketServerProtocolHandler("/webSocket", "WebSocket", true, 65536 * 10));
                    // 添加 HTTP 编解码器
                    /*ch.pipeline().addLast(new HttpServerCodec());
                    ch.pipeline().addLast(new HttpObjectAggregator(65536)); // 处理消息的最大长度
                    pipeline.addLast(new ChunkedWriteHandler());
                    ch.pipeline().addLast(new WebSocketServerProtocolHandler("/websocket"));*/
                    //TODO 服务端发送消息时编码 编码后自定义客户端能正确收到消息
                    ch.pipeline().addLast(new StringEncoder());
                    ch.pipeline().addLast(new CustomByteToNbMessageDecoder());



                    //TODO 能监听到Android 发送消息的方式
                    ch.pipeline().addLast(new CustomHandler()); // 自定义的请求处理器
                    //ch.pipeline().addLast(httpServerHandler); // 自定义的 HTTP 请求处理器
                    // 自定义的webSocketHandler，处理业务逻辑
                    //ch.pipeline().addLast(webSocketHandler);

                }
            });

            /*Channel channel = serverBootstrap.bind(port).sync().channel();
            log.info("=============绑定端口:{}================",port);
            channel.closeFuture().sync();*/
            //绑定端口号，同步启动服务端
            ChannelFuture channelFuture = serverBootstrap.bind("192.168.2.120",1024).sync();
            log.info("=============绑定端口:{}================", channelFuture.channel().localAddress());
            //对关闭通道进行监听
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}
